import os
import torch
import torch.nn as nn
import torchvision.models as models
import onnxruntime as ort
import numpy as np


class AlexNetImpl(nn.Module):
    def __init__(self, num_classes: int = 1000, dropout: float = 0.5) -> None:
        super().__init__()
        # _log_api_usage_once(self)
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(
            nn.Dropout(p=dropout),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(p=dropout),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.features(x)
        # x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x


class Alexnet(object):
    def __init__(self):
        model_weights = 'C:/Users/liyu/.cache/torch/hub/checkpoints/alexnet-owt-7be5be79.pth'
        self.net = AlexNetImpl(num_classes=1000)
        state_dict = torch.load(model_weights, map_location='cpu')
        self.net.load_state_dict(state_dict, strict=False)
        self.net.eval()
    
    def forward(self, inputs):
        return self.net(inputs)

    def export(self, onnx_path, input_size=224):
        inputs = torch.randn(1, 3, input_size, input_size)
        torch.onnx.export(self.net, inputs, onnx_path, 
            verbose=True,
            opset_version=11, 
            input_names=['input'], 
            output_names=['output'], 
        )


class SampleOnnx(object):
    def __init__(self, onnxpath):
        self.session = ort.InferenceSession(onnxpath)
    
    def infer(self, x):
        input_dict = {
            'input': x,
        }
        output_names = ['output']
        outputs = self.session.run(output_names, input_dict)
        return outputs[0]


def test_onnx(onnxpath, input_size):
    inputs = np.ones((1, 3, input_size, input_size), dtype=np.float32)
    demo = SampleOnnx(onnxpath)
    y = demo.infer(inputs)
    y1 = y.reshape((-1))
    size = y1.shape[0]
    step = 1
    if size > 1000:
        step = 1
    for i in range(size):
        if i % step == 0: 
            print(y1[i])
    print(y.shape)
    print(np.sum(y))


if __name__ == '__main__':
    onnx_path = '../data/alexnet.onnx'
    # if not os.path.exists(onnx_path):
    input_size = 224
    if True:
        model = Alexnet()
        model.export(onnx_path, input_size)
    test_onnx(onnx_path, input_size)
